package com.mingren.manager.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.mingren.manager.websocket.disruptor.PictureEditEventProducer;
import com.mingren.manager.websocket.model.PictureEditRequestMessage;
import com.mingren.manager.websocket.model.PictureEditResponseMessage;
import com.mingren.model.domain.User;
import com.mingren.model.enums.PictureEditActionEnum;
import com.mingren.model.enums.PictureEditMessageTypeEnum;
import com.mingren.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 曹宏福
 * @version 1.0
 * WebSocket处理器:连接成功、连接关闭、收到客户端消息时进行相应的处理
 */
@Component
@Slf4j
public class PictureEditHandler extends TextWebSocketHandler {


    @Resource
    private UserService userService;

    /**
     * Disruptor 生产者
     */
    @Resource
    private PictureEditEventProducer pictureEditEventProducer;

    // 每张图片的编辑状态 值:pictureId 值:正在编辑图片的用户id
    private final Map<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>(); //保证线程安全
    // 保存所有连接的会话,key:pictureID , value:用户会话集合
    private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>();

    /**
     * 接收到客户端消息->根据消息类型执行不同的处理
     *
     * @param session：用户session
     * @param message：
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        //将消息解析为PictureEditMessage
        String payload = message.getPayload();
        PictureEditRequestMessage pictureEditRequestMessage = JSONUtil.toBean(payload, PictureEditRequestMessage.class);
        //String type = pictureEditRequestMessage.getType();
        //根据消息类型获得图片编辑枚举类->根据不同类型的枚举 进行不同的操作
        //PictureEditMessageTypeEnum pictureEditMessageTypeEnum = PictureEditMessageTypeEnum.getEnumByValue(type);

        //从Session属性中获取公共参数
        Map<String, Object> attributes = session.getAttributes();
        User user = (User) attributes.get("user");
        Long pictureId = (Long) attributes.get("pictureId");

        //生产消息
        pictureEditEventProducer.publishEvent(pictureEditRequestMessage, session, user, pictureId);
    }

    /**
     * 进入编辑
     *
     * @param pictureEditRequestMessage
     * @param session
     * @param user
     * @param pictureId
     * @throws Exception
     */
    public void handleEnterEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        // 没有用户正在编辑该图片，才能进入编辑
        if (!pictureEditingUsers.containsKey(pictureId)) {
            //设置当前用户为编辑用户
            pictureEditingUsers.put(pictureId, user.getId());
            //向其他客户端发送信息 表明当前用户正在操作该图片
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue());
            pictureEditResponseMessage.setMessage(String.format("%s正在编辑图片", user.getUserName()));
            pictureEditResponseMessage.setUser(userService.getUserVo(user));
            broadcastToPicture(pictureId, pictureEditResponseMessage, null);
        }
    }

    /**
     * 退出编辑状态
     *
     * @param pictureEditRequestMessage
     * @param session
     * @param user
     * @param pictureId
     * @throws Exception
     */
    public void handleExitEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        Long editingUserId = pictureEditingUsers.get(pictureId);
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            // 移除当前用户的编辑状态
            pictureEditingUsers.remove(pictureId);
            // 构造响应，发送退出编辑的消息通知
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue());
            String message = String.format("%s退出编辑图片", user.getUserName());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setUser(userService.getUserVo(user));
            broadcastToPicture(pictureId, pictureEditResponseMessage);
        }
    }

    /**
     * 执行编辑操作
     *
     * @param pictureEditRequestMessage:
     * @param session:
     * @param user:
     * @param pictureId:
     * @throws Exception
     */
    public void handleEditActionMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        //获取编辑此图片的用户ID
        Long editingUserId = pictureEditingUsers.get(pictureId);
        //获取编辑请求动作类型
        String editAction = pictureEditRequestMessage.getEditAction();
        //获取动作类型枚举类
        PictureEditActionEnum pictureEditActionEnum = PictureEditActionEnum.getEnumByValue(editAction);
        //判空
        if (pictureEditActionEnum == null) {
            //如果没有要执行的动作直接返回
            return;
        }
        //确认是当前编辑者
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setEditAction(editAction);
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EDIT_ACTION.getValue());
            pictureEditResponseMessage.setMessage(String.format("%s执行%s", user.getUserName(), pictureEditActionEnum.getText()));
            pictureEditResponseMessage.setUser(userService.getUserVo(user));
            //广播给其他用户除了自己
            broadcastToPicture(pictureId, pictureEditResponseMessage, session);
        }
    }

    /**
     * 建立连接后执行的方法->将会话保存到map集合中,并且给其他会话发送消息->该用户加入编辑
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet());
        pictureSessions.get(pictureId).add(session);

        //构造图片编辑响应
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();

        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        pictureEditResponseMessage.setMessage(String.format("%s正在编辑图片", user.getUserName()));
        pictureEditResponseMessage.setUser(userService.getUserVo(user));

        //广播给同一张图片的用户
        broadcastToPicture(pictureId, pictureEditResponseMessage);
    }

    /**
     * 全部广播
     *
     * @param pictureId:图片id
     * @param pictureEditResponseMessage:图片编辑响应
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage) throws Exception {
        // 图片id,图片编辑响应,排除的为空
        broadcastToPicture(pictureId, pictureEditResponseMessage, null);
    }


    /**
     * 广播消息->排除已经退出会话的用户
     *
     * @param pictureId:图片id
     * @param pictureEditResponseMessage:图片编辑响应
     * @param excludeSession:排除的用户session
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage, WebSocketSession excludeSession) throws Exception {
        //获取到当前会话中的用户集合session
        Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId);
        //判空
        if (CollUtil.isNotEmpty(sessionSet)) {
            // 创建 ObjectMapper
            ObjectMapper objectMapper = new ObjectMapper();
            // 配置序列化：将 Long 类型转为 String，解决丢失精度问题
            SimpleModule module = new SimpleModule();
            module.addSerializer(Long.class, ToStringSerializer.instance);
            module.addSerializer(Long.TYPE, ToStringSerializer.instance);
            objectMapper.registerModule(module);
            //序列化为JSON字符串
            String message = objectMapper.writeValueAsString(pictureEditResponseMessage);
            TextMessage textMessage = new TextMessage(message);
            for (WebSocketSession session : sessionSet) {
                // 排除掉已经退出编辑的session用户->不发送
                if (excludeSession != null && excludeSession.equals(session)) {
                    continue;
                }
                if (session.isOpen()) {
                    //如果当前的session会话处于打开状态 发送
                    session.sendMessage(textMessage);
                }
            }
        }
    }

    /**
     * WebSocket连接关闭时,需要移除当前用户的编辑状态、并且从集合中删除当前会话、还可以给其他客户端发送信息
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
        //从session中获取属性信息
        Map<String, Object> attributes = session.getAttributes();
        User user = (User) attributes.get("user");
        Long pictureId = (Long) attributes.get("pictureId");
        //移除当前用户的编辑信息
        handleExitEditMessage(null, session, user, pictureId);
        //删除用户会话
        Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId);
        if (!CollUtil.isEmpty(sessionSet)) {
            //移除当前会话
            sessionSet.remove(session);
            if (sessionSet.isEmpty()) {
                pictureSessions.remove(pictureId);
            }
        }
        //创建响应体
        //构造图片编辑响应
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();

        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        pictureEditResponseMessage.setMessage(String.format("%s离开编辑", user.getUserName()));
        pictureEditResponseMessage.setUser(userService.getUserVo(user));
        //发送通知
        broadcastToPicture(pictureId, pictureEditResponseMessage);
    }
}
